package controlisolation.lifecyclefsm;

import controlisolation.lifecyclefsm.events.LifeCycleExitForcedEvent;
import controlisolation.lifecyclefsm.events.LifeCycleExitForcedEventListener;
import controlisolation.lifecyclefsm.stateexecutor.StateWork;
import controlisolation.lifecyclefsm.stateexecutor.StateExecutor;

import controlisolation.lifecyclefsm.stateexecutor.WorkFinishedEventListener;

import controlisolation.lifecyclefsm.stateexecutor.WorkErrorEventListener;
import controlisolation.lifecyclefsm.stateexecutor.WorkInfoMessageEventListener;
import controlisolation.lifecyclefsm.stateexecutor.WorkStartedEventListener;
import controlisolation.lifecyclefsm.stateexecutor.WorkStatus;
import controlisolation.lifecyclefsm.stateexecutor.WorkWarningEventListener;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.commons.scxml.env.AbstractStateMachine;
import org.apache.commons.scxml.model.State;

public class LifeCycleFSM extends AbstractStateMachine
        implements
        Runnable,
        WorkStartedEventListener,
        WorkFinishedEventListener,
        WorkErrorEventListener,
        WorkWarningEventListener,
        WorkInfoMessageEventListener
         {

    private final static Logger _logger = Logger.getLogger(LifeCycleFSM.class.getName());
    private List _workStartedListeners = new ArrayList(),
            _workFinishedListeners = new ArrayList(),
            _workErrorsListeners = new ArrayList(),
            _workWarningsListeners = new ArrayList(),
            _workInfoMessagesListeners = new ArrayList(),
            _lifeCycleExitForcedEventListener=new ArrayList();
    private Map<String, Object> _globals;
    private Map<String, Object> _lastExecutedWorkOutput;
    private SCXMLListenerSocketServer _fsmStateServer;
    private LifeCycleFSMController _controller;
    private boolean _debugMessages = false;
    private boolean _running=false;
    
    /**
     * State Machine uses this scmxml config file
     */
    //private String ScxmlConfigFileName = 
    //        "./ForanManagerLifeCyle.scxml";
    //"file:///c|/temp/ForanManagerLifeCyle.scxml";
    private Map<String, StateExecutor> _stateExecutors;

    public LifeCycleFSM(String scxmlFileName) {
        super(LifeCycleFSM.class.getResource(scxmlFileName));
    }

    public LifeCycleFSM(URL url) {
        super(url);
    }

    public boolean isInitial(State state) {
        return getEngine().getStateMachine().getInitial().equals(state.getId());
    }

    public void bindTo(LifeCycleFSMController controller) {
        this.setController(controller);
        controller.setLfsm(this);
    }

    public void setWorkForState(String stateName, StateWork work) {
        getStateExecutors().get(stateName).setWork(work);
    }

    public StateWork getWorkForState(String stateName) {
        return getStateExecutors().get(stateName).getWork();
    }

    /*
    private void take_control() {
        while (_controller.isRunning()) {
            try {
                _controller.controlCycle();
            } catch (Exception ex) {
                _logger.log(Level.SEVERE, null, ex);
            }
        }
    }

    
    public void start() {
        _controller.run();
        take_control();
    }*/

    @Override
    public void run() {
         _running=true;
         while (isRunning()) 
         {
             try{
                
                    _controller.controlCycle();
                    
                 
             } catch (Exception ex) {
                _logger.log(Level.SEVERE, null, ex);
            }
         }
         //this.fireLifeCycleExitForcedEvent();
    }

    public void stop() {
        _running=false;
        this.fireLifeCycleExitForcedEvent();
    }

    private void instanceStateExecutors() {


        for (Object key : getEngine().getStateMachine().getChildren().keySet()) {
            Object children_entry = getEngine().getStateMachine().getChildren().get(key);
            if (children_entry instanceof State) {
                State state = (State) children_entry;
                StateExecutor stateExecutor = new StateExecutor(state.getId(), this);
                stateExecutor.addWorkStartedEventListener(this);
                stateExecutor.addWorkFinishedEventListener(this);
                stateExecutor.addWorkErrorEventListener(this);
                stateExecutor.addWorkWarningEventListener(this);
                stateExecutor.addWorkInfoMessageEventListener(this);
                getStateExecutors().put(state.getId(), stateExecutor);
            }
        }
    }

    public boolean fireFSMEvent(String fsmevent) {
        if (isDebugMessages()) {
            _logger.log(Level.INFO, "Firing event:" + fsmevent);
        }
        return fireEvent(fsmevent);
    }

    /* HELPER METHOD(S) */
    @Override
    public boolean invoke(String name) {
        if (getStateExecutors() == null) {
            _stateExecutors = new HashMap<>();
            _globals = new HashMap<>();
            instanceStateExecutors();
        }

        StateExecutor stateExecutor = this.getStateExecutors().get(name);

        if (stateExecutor.getWork() == null) {
            try {
                if (!isInitial(this.getCurrentState())) {
                    _logger.log(Level.WARNING, name + " No work defined.");

                }
            } catch (Exception e) {
            }
            return false;
        }

        try {

            copyPreviousStateOutputIntoCurrentStateInput(stateExecutor);

            stateExecutor.doWork();
            _lastExecutedWorkOutput = new HashMap<String, Object>(stateExecutor.getWork().getWorkOutputs());

        } catch (Exception e) {
            _logger.log(Level.SEVERE, name + " Work Exception:" + e.toString());
            e.printStackTrace();
            return false;
        }
        return true;
    }

    /*
     * Get current state ID as string
     */
    public String getCurrentStateId() {
        Set states = getEngine().getCurrentStatus().getStates();
        State state = (State) states.iterator().next();
        
        return state.getId();
    }

    /*
     * Get current state as apache's State object
     */
    public org.apache.commons.scxml.model.State getCurrentState() {
        Set states = getEngine().getCurrentStatus().getStates();
        return ((org.apache.commons.scxml.model.State) states.iterator().next());
    }

    /*
     * Get events belongs to current status of the FSM
     */
    public Collection getCurrentStateEvents() {

        return getEngine().getCurrentStatus().getEvents();
    }

    public State getState(String name) {
        return (State) getEngine().getStateMachine().getChildren().get(name);
    }

    /**
     * @return the _stateExecutors
     */
    public Map<String, StateExecutor> getStateExecutors() {
        return _stateExecutors;
    }

    public StateWork getCurrentStateWork() {
        return getStateExecutors().get(this.getCurrentState().getId()).getWork();
    }

    @Override
    public void handleWorkStartedEvent(StateWork w) {
        this.fireWorkStartedEvent(w);
    }

    @Override
    public void handleWorkFinishedEvent(StateWork w) {
        this.fireWorkFinishedEvent(w);
    }

    

    public synchronized void addLifeCycleExitForcedEventListener(LifeCycleExitForcedEventListener listener) {
        _lifeCycleExitForcedEventListener.add(listener);
    }
    
    public synchronized void removeLifeCycleExitForcedEventListener(LifeCycleExitForcedEventListener listener) {
        _lifeCycleExitForcedEventListener.remove(listener);
    }
    

    public synchronized void addWorkStartedEventListener(WorkErrorEventListener listener) {
        _workStartedListeners.add(listener);
    }

    public synchronized void removeWorkStartedEventListener(WorkErrorEventListener listener) {
        _workStartedListeners.remove(listener);
    }

    public synchronized void addWorkFinishedEventListener(WorkFinishedEventListener listener) {
        _workFinishedListeners.add(listener);
    }

    public synchronized void removeWorkFinishedEventListener(WorkFinishedEventListener listener) {
        _workFinishedListeners.remove(listener);
    }

    public synchronized void addWorkErrorEventListener(WorkErrorEventListener listener) {
        _workErrorsListeners.add(listener);
    }

    public synchronized void removeWorkErrorEventListener(WorkFinishedEventListener listener) {
        _workErrorsListeners.remove(listener);
    }
    
    public synchronized void addWorkWarningEventListener(WorkWarningEventListener listener) {
        _workWarningsListeners.add(listener);
    }

    public synchronized void removeWorkWarningEventListener(WorkWarningEventListener listener) {
        _workWarningsListeners.remove(listener);
    }

  
    public synchronized void addWorkInfoMessagesEventListener(WorkInfoMessageEventListener listener) {
        _workInfoMessagesListeners.add(listener);
    }

    public synchronized void removeWorkInfoMessageEventListener(WorkInfoMessageEventListener listener) {
        _workInfoMessagesListeners.remove(listener);
    }

    
  

    private synchronized void fireWorkStartedEvent(StateWork w) {
        //WorkStartedEvent event = new WorkStartedEvent(w);

        if (_debugMessages) {
            _logger.log(Level.INFO, "WORK_STARTED:{0}", w.toString());
        }

        Iterator i = _workStartedListeners.iterator();
        while (i.hasNext()) {
            ((WorkStartedEventListener) i.next()).handleWorkStartedEvent(w);
        }
    }
    
    private synchronized void fireLifeCycleExitForcedEvent() {
       LifeCycleExitForcedEvent event = new LifeCycleExitForcedEvent(this);

        if (_debugMessages) {
            _logger.log(Level.INFO, "FSM FORCED TO FINISH");
        }

        Iterator i = _lifeCycleExitForcedEventListener.iterator();
        while (i.hasNext()) {
            ((LifeCycleExitForcedEventListener) i.next()).handleLifeCycleExitForcedEvent(event);
        }
    }

    private synchronized void fireWorkFinishedEvent(StateWork w) {
        //WorkFinishedEvent event = new WorkFinishedEvent(w);

        if (_debugMessages) {
            _logger.log(Level.INFO, "WORK_FINISHED:{0}", w.toString());
        }
        Iterator i = _workFinishedListeners.iterator();
        while (i.hasNext()) {
            ((WorkFinishedEventListener) i.next()).handleWorkFinishedEvent(w);
        }
    }

    private synchronized void fireWorkErrorEvent(WorkStatus st) {
        //WorkStartedEvent event = new WorkStartedEvent(w);

        if (_debugMessages) {
            _logger.log(Level.INFO, "WORK ERROR:{0}", st.toString());
        }

        Iterator i = _workErrorsListeners.iterator();
        while (i.hasNext()) {
            ((WorkErrorEventListener) i.next()).handleWorkErrorEvent(st);
        }
    }
    
    private synchronized void fireWorkWarningEvent(WorkStatus st) {
        //WorkStartedEvent event = new WorkStartedEvent(w);

        if (_debugMessages) {
            _logger.log(Level.INFO, "WORK WARNING:{0}", st.toString());
        }

        Iterator i = _workWarningsListeners.iterator();
        while (i.hasNext()) {
            ((WorkWarningEventListener) i.next()).handleWorkWarningEvent(st);
        }
    }
    
    private synchronized void fireWorkInfoMessageEvent(WorkStatus st) {
        //WorkStartedEvent event = new WorkStartedEvent(w);

        if (_debugMessages) {
            _logger.log(Level.INFO, "WORK INFO MESSAGE:{0}", st.toString());
        }

        Iterator i = _workInfoMessagesListeners.iterator();
        while (i.hasNext()) {
            ((WorkInfoMessageEventListener) i.next()).handleWorkInfoMessageEvent(st);
        }
    }
    
    
    @Override
    public void handleWorkErrorEvent(WorkStatus s) {
       fireWorkErrorEvent(s);
    }
   
    @Override
    public void handleWorkWarningEvent(WorkStatus s) {
        fireWorkWarningEvent(s);
    }

    @Override
    public void handleWorkInfoMessageEvent(WorkStatus s) {
        fireWorkInfoMessageEvent(s);
    }

    private void copyPreviousStateOutputIntoCurrentStateInput(StateExecutor stateExecutor) {
        if (getLastExecutedWorkOutput() != null) {
            stateExecutor.getWork().getWorkInputs().putAll(getLastExecutedWorkOutput());
        }
    }

    public Map<String, Object> getLastExecutedWorkOutput() {
        return _lastExecutedWorkOutput;
    }

    public Map<String, Object> getGlobals() {
        return _globals;
    }

    public void startRemoteDebugServer(int port) {
        if (_fsmStateServer == null) {
            _fsmStateServer = new SCXMLListenerSocketServer("127.0.0.1", port);
            this.getEngine().addListener(this.getEngine().getStateMachine(), _fsmStateServer);
        }
    }

    public LifeCycleFSMController getController() {
        return _controller;
    }

    public void setController(LifeCycleFSMController controller) {
        this._controller = controller;
    }

    public boolean isDebugMessages() {
        return _debugMessages;
    }

    public void setDebugMessages(boolean debugMessages) {
        this._debugMessages = debugMessages;
    }

    public boolean isRunning() {
        return _running;
    }

    public void setRunning(boolean running) {
        this._running = running;
    }


    

   
}
